
#include <map>
#include <BOpenGL>
#include <BFont>
#include <BImage>
#include <BImage>
#include <BWidget>
#include <BTexture>
#include <BEvent>
#include <BApplication>
#include <BPainter>
#include "member_BPainter.h"
#include "member_BWidget.h"
#include "member_BImage.h"
#include "member_BFont.h"
#include "member_BCamera.h"
#include "member_BDisplay.h"
#include "member_BStyle.h"
#include "IGlobal.h"

using namespace BWE;

#define member						(*(member_BPainter*)_ptr)
#define member_allocate()			_ptr = new member_BPainter(this)
#define member_release()			delete (member_BPainter*)_ptr

BPainter::BPainter(const BWidget* widget)
{
	member_allocate();
	member.save();

	const IPaint& paint = widget_member(widget)->paint;
	member.widget = widget;
	member.font = paint.font;
	member.style = paint.style;
	member.size = widget_member(widget)->size;

	glBindFramebuffer(GL_FRAMEBUFFER, widget_member(widget)->fbo);

	setClip(BRect(member.size));

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

}
BPainter::BPainter(const BDisplay* display)
{
	member_allocate();
	member.save();

	member.style = BApplication::style();
	member.font = member.style->font();
	member.size = display->size();

	setClip(BRect(member.size));

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

}
BPainter::~BPainter()
{
	member.open();
	member_release();
}

const BWidget* BPainter::widget() const
{
	return member.widget;
}
const BStyle* BPainter::style() const
{
	return member.style;
}

void BPainter::setClip(int x, int y, int width, int height)
{
	setClip(BRect(x, y, width, height));
}
void BPainter::setClip(const BRect& clip)
{
	if (member.clip != clip)
	{
		glViewport(clip.x(), member.size.height() - clip.bottom(), clip.width(), clip.height());

		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		glOrtho(clip.left() - 1, clip.right(), clip.bottom() + 1, clip.top(), -1, 1);

		member.clip = clip;
	}
}
const BRect& BPainter::clip() const
{
	return member.clip;
}

bool BPainter::setColor(const BColor& color) const
{
	if (color.a() > 1)
	{
		member.color = color;
		glColor4ub(color.r(), color.g(), color.b(), color.a());
		return true;
	}
	return false;
}
bool BPainter::setColor(BByte r, BByte g, BByte b, BByte a) const
{
	if (a > 1)
	{
		member.color.set(r, g, b, a);
		glColor4ub(r, g, b, a);
		return true;
	}
	return false;
}
bool BPainter::setColor(StyleRole colorRole) const
{
	BColor color = member.style->color(colorRole);
	if (color.a() > 1)
	{
		member.color = color;
		glColor4ub(color.r(), color.g(), color.b(), color.a());
		return true;
	}
	return false;
}
bool BPainter::setColor(const char* colorName) const
{
	BColor color = member.style->color(colorName);
	if (color.a() > 1)
	{
		member.color = color;
		glColor4ub(color.r(), color.g(), color.b(), color.a());
		return true;
	}
	return false;
}
const BColor& BPainter::color() const
{
	return member.color;
}

void BPainter::setTexture(BTexture* texture)
{
	member.texture = texture;
	if(texture)
	{
		glEnable(GL_TEXTURE_2D);
		texture->bind();
	}
	else
	{
		glBindTexture(GL_TEXTURE_2D, 0);
		glDisable(GL_TEXTURE_2D);
	}
}

void BPainter::bind(BWidget* widget)
{
	if (widget)
	{
		if (widget_member(widget)->dirty)
		{
			widget_member(widget)->frame();
		}
		glEnable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, widget_member(widget)->tbo);
	}
}
void BPainter::bind(BCamera* camera)
{
	if (camera)
	{
		setTexture(camera_member(camera)->pixmap);
	}
}
void BPainter::unbind()
{
	glBindTexture(GL_TEXTURE_2D, 0);
	glDisable(GL_TEXTURE_2D);
}

void BPainter::setFont(const BFont* font)
{
	member.font = font;
}
const BFont* BPainter::font() const
{
	return member.font;
}

void BPainter::setPointSmooth(bool pointSmooth)
{
	if(pointSmooth)
	{
		glEnable(GL_POINT_SMOOTH);
	}
	else
	{
		glDisable(GL_POINT_SMOOTH);
	}
	member.pointSmooth = pointSmooth;
}
bool BPainter::pointSmooth() const
{
	return member.pointSmooth;
}

void BPainter::setPointSize(int size)
{
	glPointSize((GLfloat)size);
	member.pointSize = size;
}
int BPainter::pointSize() const
{
	return member.pointSize;
}

void BPainter::setLineSmooth(bool lineSmooth)
{
	if(lineSmooth)
	{
		glEnable(GL_LINE_SMOOTH);
	}
	else
	{
		glDisable(GL_LINE_SMOOTH);
	}
	member.lineSmooth = lineSmooth;
}
bool BPainter::lineSmooth() const
{
	return member.lineSmooth;
}

void BPainter::setLineWidth(int width)
{
	glLineWidth((GLfloat)width);
	member.lineWidth = width;
}
int BPainter::lineWidth() const
{
	return member.lineWidth;
}

void BPainter::drawPoint(const BPoint& point) const
{
	drawPoint(point.x(), point.y());
}
void BPainter::drawPoint(int x, int y) const
{
	if(member.pointSize > 0)
	{
		glBegin(GL_POINTS);
			glVertex2f((GLfloat)x, (GLfloat)y);
		glEnd();
	}
}

void BPainter::drawline(const BLine2& line) const
{
	const BPoint& start = line.start();
	const BPoint& end = line.end();
	drawLine(start.x(), start.y(), end.x(), end.y());
}
void BPainter::drawLine(const BPoint& p0, const BPoint& p1) const
{
	drawLine(p0.x(), p0.y(), p1.x(), p1.y());
}
void BPainter::drawLine(int x0, int y0, int x1, int y1) const
{
	glBegin(GL_LINES);
		glVertex2i(x0, y0);
		glVertex2i(x1, y1);
	glEnd();
}

void BPainter::drawLines(const BPoint* points, int size) const
{
	size /= 2;
	glBegin(GL_LINES);
	for(int i = 0; i < size; i++)
	{
		glVertex2i(points[i*2+0].x(), points[i*2+0].y());
		glVertex2i(points[i*2+1].x(), points[i*2+1].y());
	}
	glEnd();
}
void BPainter::drawLines(const BPointArray& points) const
{
	drawLines(points.data(), points.size());
}

void BPainter::drawLineLoop(const BPoint* points, int size) const
{
	if(size < 3)
		return;
	glBegin(GL_LINE_LOOP);
	for(int i = 0; i < size; i++)
	{
		glVertex2i(points[i].x(), points[i].y());
	}
	glEnd();
}
void BPainter::drawLineLoop(const BPointArray& points) const
{
	drawLineLoop(points.data(), points.size());
}

void BPainter::drawLineStrip(const BPoint* points, int size) const
{
	if(size < 3)
		return;
	glBegin(GL_LINE_STRIP);
	for(int i = 0; i < size; i++)
	{
		glVertex2i(points[i].x(), points[i].y());
	}
	glEnd();
}
void BPainter::drawLineStrip(const BPointArray& points) const
{
	drawLineStrip(points.data(), points.size());
}

void BPainter::drawTriangle(int x0, int y0, int x1, int y1, int x2, int y2)
{
	glBegin(GL_LINE_LOOP);
		glVertex2i(x0, y0);
		glVertex2i(x1, y1);
		glVertex2i(x2, y2);
	glEnd();
}
void BPainter::drawTriangle(const BPoint& p0, const BPoint& p1, const BPoint& p2)
{
	drawTriangle(p0.x(), p0.y(), p1.x(), p1.y(), p2.x(), p2.y());
}
void BPainter::fillTriangle(int x0, int y0, int x1, int y1, int x2, int y2)
{
	glBegin(GL_TRIANGLES);
		glVertex2i(x0, y0);
		glVertex2i(x1, y1);
		glVertex2i(x2, y2);
	glEnd();
}
void BPainter::fillTriangle(const BPoint& p0, const BPoint& p1, const BPoint& p2)
{
	fillTriangle(p0.x(), p0.y(), p1.x(), p1.y(), p2.x(), p2.y());
}

void BPainter::drawRect(const BRect& rect) const
{
	drawRect(rect.x(), rect.y(), rect.width(), rect.height());
}
void BPainter::drawRect(const BPoint& pos, const BSize& size) const
{
	drawRect(pos.x(), pos.y(), size.width(), size.height());
}
void BPainter::drawRect(int x, int y, int width, int height) const
{
	if(member.lineWidth > 0)
	{
		glBegin(GL_LINE_LOOP);
			glVertex2i(x, y);
			glVertex2i(x, y + height);
			glVertex2i(x + width, y + height);
			glVertex2i(x + width, y);
		glEnd();
	}
}

void BPainter::fillRect(const BRect& rect) const
{
	fillRect(rect.x(), rect.y(), rect.width(), rect.height());
}
void BPainter::fillRect(const BPoint& pos, const BSize& size) const
{
	fillRect(pos.x(), pos.y(), size.width(), size.height());
}
void BPainter::fillRect(int x, int y, int width, int height) const
{
	if (width < 1 || height < 1)
		return;
	if (member.texture)
	{
		glBegin(GL_QUADS);
			glTexCoord2f(0.0f, 0.0f);		glVertex2i(x, y);
			glTexCoord2f(0.0f, 1.0f);		glVertex2i(x, y + height);
			glTexCoord2f(1.0f, 1.0f);		glVertex2i(x + width, y + height);
			glTexCoord2f(1.0f, 0.0f);		glVertex2i(x + width, y);
		glEnd();
	}
	else
	{
		glBegin(GL_QUADS);
			glVertex2i(x, y);
			glVertex2i(x, y + height);
			glVertex2i(x + width, y + height);
			glVertex2i(x + width, y);
		glEnd();
	}
}

void BPainter::drawEllipse(int x, int y, int width, int height) const
{

}
void BPainter::drawEllipse(const BPoint& pos, const BSize& size) const
{

}
void BPainter::drawEllipse(const BPoint& center, int radiusX, int radiusY) const
{

}

void BPainter::drawPolygon(const BPointArray& points) const
{
	if(points.empty() || member.lineWidth < 0)
		return;
	glBegin(GL_LINE_LOOP);
	for(int i = 0; i < points.size(); i++)
	{
		glVertex2i(points[i].x(), points[i].y());
	}
	glEnd();
}
void BPainter::fillPolygon(const BPointArray& points) const
{
	if(points.empty() || member.lineWidth < 0)
		return;
	glBegin(GL_POLYGON);
	for(int i = 0; i < points.size(); i++)
	{
		glVertex2i(points[i].x(), points[i].y());
	}
	glEnd();
}

void BPainter::drawText(const BPoint& pos, const BString& text) const
{
	drawText(pos.x(), pos.y(), text);
}
void BPainter::drawText(int x, int y, const BString& text) const
{
	if(text.empty() || !member.font)
		return;
	
	const BFont* font = member.font;
	int fontHeight = font->size().height();

	int posX = x;
	int posY = y - 0.2 * fontHeight;
	for (BString::const_iterator cit = text.begin(); cit < text.end(); cit++)
	{
		unsigned short code = cit.value();

		if (code == '\n')
		{
			posX = x;
			posY += fontHeight;
			continue;
		}

		int codeWidth = member.font->codeWidth(code);

		if (code == ' ')
		{
			posX += codeWidth;
			continue;
		}
		if(code == '\t')
		{
			int times = (posX - x) / codeWidth + 1;
			posX = x + times * codeWidth;
			continue;
		}
		CodeInfo* info = font_member(font)->getCodeInfo(code);
		if (!info)
		{
			continue;
		}
		const BImage* image = info->bitmap;
		if (image)
		{
			int left = info->left;
			int top = info->top;

			int bX = posX + left;
			int bY = posY + fontHeight - top;
			int bW = image->width();
			int bH = image->height();

			drawImage(bX, bY, bW, bH, image);
		}

		posX += codeWidth;
	}
}
void BPainter::drawText(const BRect& rect, const BString& text, bool autoline) const
{
	drawText(rect.x(), rect.y(), rect.width(), rect.height(), text, autoline);
}
void BPainter::drawText(const BPoint& pos, const BSize& size, const BString& text, bool autoline) const
{
	drawText(pos.x(), pos.y(), size.width(), size.height(), text, autoline);
}
void BPainter::drawText(int x, int y, int width, int height, const BString& text, bool autoline) const
{
	if (member.color.a() != 255)
		int a = 0;
	if (text.size() && member.font)
	{
		BRect save_clip = member.clip;
		BRect clip = save_clip & BRect(x, y, width, height);
		((BPainter*)this)->setClip(clip);

		const BFont* font = member.font;
		int fontHeight = font->height();

		int posX = x;
		int posY = y - 0.2 * fontHeight;
		for (BString::const_iterator cit = text.begin(); cit != text.end(); cit++)
		{
			if (posY > y + height)//can't draw any code.
				break;

			BCode code = cit.value();
			BSize codeSize = member.font->codeSize(code);

			if (code == '\n')//new line.
			{
				posX = x;
				posY += fontHeight;
				continue;
			}
			if (code == '\t')
			{
				int times = (posX - x) / codeSize.width() + 1;
				posX = times * codeSize.width() + x;
				continue;
			}
			CodeInfo* info = font_member(font)->getCodeInfo(code);
			if (info && info->bitmap)
			{
				const BImage* image = info->bitmap;
				int left = info->left;
				int top = info->top;

				int bX = posX + left;
				int bY = posY + fontHeight - top;
				int bW = image->width();
				int bH = image->height();

				drawImage(bX, bY, bW, bH, image);
			}
			else
			{
				drawRect(posX, posY, codeSize.width(), codeSize.height());
			}
			if (posX + codeSize.width() > x + width)//can't draw any code.
			{
				if (!autoline)
					break;
				posX = x;
				posY += fontHeight;
			}
			posX += codeSize.width();
		}
		((BPainter*)this)->setClip(save_clip);
	}
}

void BPainter::drawImage(const BRect& rect, const BImage* image) const
{
	drawImage(rect.x(), rect.y(), rect.width(), rect.height(), image);
}
void BPainter::drawImage(const BPoint& pos, const BSize& size, const BImage* image) const
{
	drawImage(pos.x(), pos.y(), size.width(), size.height(), image);
}
void BPainter::drawImage(int x, int y, int width, int height, const BImage* image) const
{
	if (!image)
		return;

	glEnable(GL_TEXTURE_2D);

	image->bind();
	glBegin(GL_QUADS);
	glTexCoord2f(0, 0);		glVertex2i(x, y);
	glTexCoord2f(0, 1);		glVertex2i(x, y + height);
	glTexCoord2f(1, 1);		glVertex2i(x + width, y + height);
	glTexCoord2f(1, 0);		glVertex2i(x + width, y);
	glEnd();
	image->unbind();

	glDisable(GL_TEXTURE_2D);
}

void BPainter::drawBackground(State state)
{
	StyleRole colorRole = Style_None;
	StyleRole imageRole = Style_None;
	switch (state)
	{
	case State_Normal:
		colorRole = Color_Background;
		imageRole = Image_Background;
		break;
	case State_Hovered:
		colorRole = Color_Background_Hovered;
		imageRole = Image_Background_Hovered;
		break;
	case State_Pressed:
		colorRole = Color_Background_Pressed;
		imageRole = Image_Background_Pressed;
		break;
	case State_Disable:
		colorRole = Color_Background_Disable;
		imageRole = Image_Background_Disable;
		break;
	case State_Checked:
		colorRole = Color_Background_Checked;
		imageRole = Image_Background_Checked;
		break;
	default:
		return;
	}
	BRect rect = member.widget->size();
	if (setColor(colorRole))
		fillRect(rect);
	if (BImage* image = member.style->image(imageRole))
		drawImage(rect, image);
}
void BPainter::drawContent(State state)
{
	StyleRole colorRole = Style_None;
	StyleRole imageRole = Style_None;
	switch (state)
	{
	case State_Normal:
		colorRole = Color_Content;
		imageRole = Color_Content;
		break;
	case State_Hovered:
		colorRole = Color_Content_Hovered;
		imageRole = Color_Content_Hovered;
		break;
	case State_Pressed:
		colorRole = Color_Content_Pressed;
		imageRole = Color_Content_Pressed;
		break;
	case State_Checked:
		colorRole = Color_Content_Checked;
		imageRole = Color_Content_Checked;
		break;
	case State_Disable:
		colorRole = Color_Content_Disable;
		imageRole = Color_Content_Disable;
		break;
	default:
		return;
	}
	const BRect& crect = member.widget->clientRect();
	if (setColor(colorRole))
		fillRect(crect);
	if (BImage* image = member.style->image(imageRole))
		drawImage(crect, image);
}
void BPainter::drawBorder()
{
	int b = member.widget->border();
	if (b > 0 && setColor(Value_Border))
	{
		int w = member.widget->width();
		int h = member.widget->height();
		fillRect(0, b, b, h - b - b);
		fillRect(0, 0, w, b);
		fillRect(w - b, b, b, h - b - b);
		fillRect(0, h - b, w, b);
	}
}
void BPainter::drawPerch()
{
	const BPerchs& perchs = widget_member(member.widget)->perchs;
	if (perchs == BPerchs())
		return;

	member_BWidget* wm = widget_member(member.widget);
	int b = wm->border;
	int w = wm->size.width();
	int h = wm->size.height();

	if (wm->dirtyRect)
		wm->freshRects();

	const BRect& topRect = wm->rectTop;
	const BRect& leftRect = wm->rectLeft;
	const BRect& rightRect = wm->rectRight;
	const BRect& bottomRect = wm->rectBottom;

	BRect leftTopRect(b, b, perchs.left(), perchs.top());
	BRect leftBottomRect(b, bottomRect.top(), perchs.left(), perchs.bottom());
	BRect rightTopRect(rightRect.left(), b, perchs.right(), perchs.top());
	BRect rightBottomRect(rightRect.left(), bottomRect.top(), perchs.right(), perchs.bottom());

	if(setColor(Color_Perch))
	{
		fillRect(leftRect);
		fillRect(topRect);
		fillRect(rightRect);
		fillRect(bottomRect);

		fillRect(leftTopRect);
		fillRect(leftBottomRect);
		fillRect(rightTopRect);
		fillRect(rightBottomRect);
	}
	const BImage* perch_left_image = member.style->image("perch-left_image");
	if (perch_left_image)
	{
		float ix = 0.0f;
		float iy = 0.0f;
		float iw = perch_left_image->size().width();
		float ih = perch_left_image->size().height();
		int x = leftRect.x();
		int y = leftRect.y();
		int w = leftRect.width();
		int h = leftRect.height();
		glBegin(GL_QUADS);
		glTexCoord2f(ix, iy);		glVertex2i(x, y);
		glTexCoord2f(ix, ih);		glVertex2i(x, y + h);
		glTexCoord2f(iw, ih);		glVertex2i(x + w, y + h);
		glTexCoord2f(iw, iy);		glVertex2i(x + w, y);
		glEnd();

	}
}
void BPainter::drawMargin()
{
	const BMargins& margins = member.widget->margins();
	if (margins == BMargins())
		return;

	int b = member.widget->border();
	int w = member.widget->width();
	int h = member.widget->height();

	const BPerchs& perchs = member.widget->perchs();
	int pleft = perchs.left();
	int pright = perchs.right();
	int ptop = perchs.top();
	int pbottom = perchs.bottom();

	int mleft = margins.left();
	int mright = margins.right();
	int mtop = margins.top();
	int mbottom = margins.bottom();

	BRect left_rect(b + pleft, b + ptop, mleft, h - b - b - ptop - pbottom);
	BRect top_rect(b + pleft, b + ptop, w - b - b - pleft - pright, mtop);
	BRect right_rect(w - b - pright - mright, b + ptop + mtop, mright, h - b - b - ptop - mtop - pbottom);
	BRect bottom_rect(b + pleft, h - b - pbottom - mbottom, w - b - b - pleft - pright, mbottom);
	BRect left_top_rect(left_rect.x(), top_rect.y(), mleft, mtop);
	BRect left_bottom_rect(left_rect.x(), bottom_rect.y(), mleft, mbottom);
	BRect right_top_rect(right_rect.x(), top_rect.y(), mright, mtop);
	BRect right_bottom_rect(right_rect.x(), bottom_rect.y(), mright, mtop);

	if (setColor(Color_Margin))
	{
		if (mleft > 0)
		{
			fillRect(left_rect);
		}
		if (mtop > 0)
		{
			fillRect(top_rect);
		}
		if (mright > 0)
		{
			fillRect(right_rect);
		}
		if (mbottom > 0)
		{
			fillRect(bottom_rect);
		}
		if (left_top_rect.valid())
		{
			fillRect(left_top_rect);
		}
		if (left_bottom_rect.valid())
		{
			fillRect(left_bottom_rect);
		}
		if (right_top_rect.valid())
		{
			fillRect(right_top_rect);
		}
		if (right_bottom_rect.valid())
		{
			fillRect(right_bottom_rect);
		}
	}
}
